#include "analex.h"


static FILE * arquivo;
static char bufferLeitura[BUFFER_LEITURA_ARQUIVO], caractereLido;
static int estado, contadorLinha, contadorColuna;
static char *lexema;
static int indiceProximoCaractere, tamanhoLexema, indiceLexema;
static TabeladeSimbolos *tabelaPalavrasReservadas;
static TabeladeSimbolos *tabelaIdentificadores;
static TabeladeSimbolos *tabelaLiterais;
static char* vetorTokens[] = {(char*) "INICIO", (char*) "FIM", (char*) "TIPO", (char*) "VETOR" , (char*) "INTEIRO",
                              (char*) "REAL", (char*) "CHAR" , (char*) "LOGICO", (char*) "REGISTRO", (char*) "FIMREGISTRO",(char*) "FUNCAO",
                              (char*) "PROCEDIMENTO", (char*) "SE",(char*) "ENTAO", (char*) "SENAO",(char*) "FIMSE",(char*) "ENQUANTO",(char*) "FACA",
                              (char*) "FIMENQUANTO", (char*) "SELETOR",(char*) "CASO",(char*) "QUEBRA",(char*) "PADRAO",(char*) "FIMSELETOR",
                              (char*) "LEIA",(char*) "IMPRIMA", (char*) "VERDADEIRO",(char*) "FALSO",(char*) "NAO",(char*) "E",(char*) "OU",
                              (char*) "ID", (char*) "NUM", (char*) "LITERAL", (char*) "PONTO", (char*) "VIRGULA",
                              (char*) "PONTOEVIRGULA", (char*) "DOISPONTOS", (char*) "IGUAL", (char*) "MAIS", (char*) "MENOS",
                              (char*) "MULTIPLICACAO", (char*) "DIVISAO", (char*) "MENOR", (char*) "DIFERENTE", (char*) "ATRIBUICAO",
                              (char*) "MENOROUIGUAL", (char*) "MAIOR", (char*) "MAIOROUIGUAL", (char*) "ABREPARENTESES", (char*) "FECHAPARENTESES",
                              (char*) "ABRECOLCHETES", (char*) "FECHACOLCHETES", (char*) "ASPAS", (char*) "ABRECHAVES", (char*) "FECHACHAVES"};



/**
Lê uma parte do arquivo e armazena em um buffer
*/
void lerBlocodeArquivo(){
    int tamanhoLeitura = fread(bufferLeitura, sizeof(char), BUFFER_LEITURA_ARQUIVO, arquivo);

    if(tamanhoLeitura < BUFFER_LEITURA_ARQUIVO) bufferLeitura[tamanhoLeitura] = EOF;
    indiceProximoCaractere = 0;
}

/**
faz o controle da posição (linha e coluna) do arquivo,
lê um único caracter e o retorna
*/

char proximoCaractere(){
    char c;
    c = bufferLeitura[indiceProximoCaractere++];
    /**controle das linhas e colunas do arquivo*/
    contadorColuna ++;
    if(c == '\n'){
       contadorLinha++;
       contadorColuna = 0;
    }
    /** teste se ja leu o buffer de leitura inteiro*/
    if(indiceProximoCaractere == BUFFER_LEITURA_ARQUIVO){
        lerBlocodeArquivo();
    }
    return c;
}

/**
Inicialializa a analise léxica
*/
void iniciaAnalex(FILE *arquivoACompilar){
    lexema = (char*) malloc(sizeof(char) * TAMANHO_PADRAO_LEXEMA);
    if(lexema == NULL) {
        errosMemoria(MEMORIA_MAL_ALOCADA);
        exit(1);
    }
    tamanhoLexema = TAMANHO_PADRAO_LEXEMA;
    contadorLinha = 1;
    contadorColuna = 0;
    indiceLexema = 0;
    arquivo = arquivoACompilar;
    lerBlocodeArquivo();
    caractereLido = proximoCaractere();

    tabelaPalavrasReservadas = new TabeladeSimbolos();
    tabelaIdentificadores = new TabeladeSimbolos();
    tabelaLiterais = new TabeladeSimbolos();

    /**
    Insere todas as palavras reservadas no objeto tabelaPalavrasReservadas
    */
    char palavraReservada[13];

    for (int i = 0; i <= (OU - INICIO); i++){
        EntradaTabelaPalavraReservada* entrada;
        entrada = new EntradaTabelaPalavraReservada();
        entrada->token = INICIO+i;
        strcpy(palavraReservada,vetorTokens[i]);
        strlwr(palavraReservada);
        tabelaPalavrasReservadas->insereEntrada(palavraReservada, entrada);
    }
}

int retornaContadorLinha(){
    return contadorLinha;
}

int retornaContadorColuna(){
    return contadorColuna;
}

char* retornaLexema(){
    return lexema;
}

/**
procedimento para formar um lexema
Caso o tamanho alocado não seja suficiente para compor o caractere
realoca mais memoria para o lexema e concatena o caractere
*/
void compoeLexema(char c){
    /**preciso sempre de 2 posições livres antes de concatenar o proximo caractere*/
    if((indiceLexema + 2) > tamanhoLexema) {
        tamanhoLexema += TAMANHO_PADRAO_LEXEMA * sizeof(char);
        lexema = (char*) realloc(lexema, tamanhoLexema);
        if (lexema == NULL) {
            errosMemoria(MEMORIA_MAL_REALOCADA);
            exit(1);
        }
    }
    lexema[indiceLexema] = c;
    lexema[++indiceLexema] = '\0';
}

/**Comando: Reinicia o automato*/
void reiniciaAutomato(){
    indiceLexema = 0;
    estado = Q0;
}

/** Implementação do automato. Retorna o próximo tokem válido encontrado ou o tokem
    especial EOF **/
int proximoToken() {
    bool achouFimdeArquivo = false;
    /**
       as variaveis abaixo guardam a linha e coluna onde iniciou um literal abriu um comentario
       Necesario para emitir a linha e coluna correta quando ha erros de eof inesperado.
    */
    unsigned int linhaErro = 0;
    unsigned int colunaErro = 0;
    estado = Q0;
    indiceLexema = 0;

    while(1) {
        switch(estado) {
            case Q0: {
                while(isspace(caractereLido)) {
                    /**Desconsidera espaços Transição (Q0, espaço, q0)*/
                    estado = Q0;
                    caractereLido = proximoCaractere();
                }
                if(isalpha(caractereLido)) {
                    /**transição (Q0, letra Q1)*/
                    estado = Q1;
                    compoeLexema(caractereLido);
                    caractereLido = proximoCaractere();
                    break;
                }
                if(isdigit(caractereLido)) {
                    /**transição (Q0, digito, Q3)*/
                    estado = Q3;
                    compoeLexema(caractereLido);
                    caractereLido = proximoCaractere();
                    break;
                }
                switch(caractereLido) {
                    case '.':
                        /**transição (Q0, '.', Q10)*/
                        estado = Q10;
                        compoeLexema(caractereLido);
                        caractereLido = proximoCaractere();
                        break;
                    case ',':
                        /**transição (Q0, ,, Q12)*/
                        estado = Q12;
                        compoeLexema(caractereLido);
                        caractereLido = proximoCaractere();
                        break;
                    case ';':
                        /**transição (Q0, ;, Q13)*/
                        estado = Q13;
                        compoeLexema(caractereLido);
                        caractereLido = proximoCaractere();
                        break;
                    case ':':
                        /**transição (Q0, :, Q14)*/
                        estado = Q14;
                        compoeLexema(caractereLido);
                        caractereLido = proximoCaractere();
                        break;
                    case '=':
                        /**transição (Q0, =, Q15)*/
                        estado = Q15;
                        compoeLexema(caractereLido);
                        caractereLido = proximoCaractere();
                        break;
                    case '+':
                        /**transição (Q0, +, Q16)*/
                        estado = Q16;
                        compoeLexema(caractereLido);
                        caractereLido = proximoCaractere();
                        break;
                    case '-':
                        /**transição (Q0, -, Q17)*/
                        estado = Q17;
                        compoeLexema(caractereLido);
                        caractereLido = proximoCaractere();
                        break;
                    case '*':
                        /**transição (Q0, *, Q18)*/
                        estado = Q18;
                        compoeLexema(caractereLido);
                        caractereLido = proximoCaractere();
                        break;
                    case '/':
                        /**transição (Q0, /, Q19)*/
                        estado = Q19;
                        compoeLexema(caractereLido);
                        caractereLido = proximoCaractere();
                        break;
                    case '<':
                        /**transição (Q0, <, Q20)*/
                        estado = Q20;
                        compoeLexema(caractereLido);
                        caractereLido = proximoCaractere();
                        break;
                    case '>':
                        /**transição (Q0, >, Q25)*/
                        estado = Q25;
                        compoeLexema(caractereLido);
                        caractereLido = proximoCaractere();
                        break;
                    case '(':
                        /**transição (Q0, (, Q28)*/
                        estado = Q28;
                        compoeLexema(caractereLido);
                        caractereLido = proximoCaractere();
                        break;
                    case ')':
                        /**transição (Q0, ), Q29)*/
                        estado = Q29;
                        compoeLexema(caractereLido);
                        caractereLido = proximoCaractere();
                        break;
                    case '[':
                        /**transição (Q0, [, Q30)*/
                        estado = Q30;
                        compoeLexema(caractereLido);
                        caractereLido = proximoCaractere();
                        break;
                    case ']':
                        /**transição (Q0, ], Q31)*/
                        estado = Q31;
                        compoeLexema(caractereLido);
                        caractereLido = proximoCaractere();
                        break;
                    case '"':
                        /**inicio de literais transição (Q0, '"', Q32)*/
                        estado = Q32;
                        linhaErro = retornaContadorLinha();
                        if (caractereLido == '\n') linhaErro--;
                        colunaErro = retornaContadorColuna();
                        caractereLido = proximoCaractere();
                        break;
                    case '{':
                        /**inicio de comentarios transição (Q0, '{', Q35)*/
                        estado = Q35;
                        linhaErro = retornaContadorLinha();
                        colunaErro = retornaContadorColuna();
                        caractereLido = proximoCaractere();
                        break;
                    case EOF: estado = Q37;
                        break;
                    default: estado = Q38;
                        break;
                }
                break;
            }
            case Q1: {
                if(isalnum(caractereLido)) {
                    /**transição (Q1, {letra,digito}, Q1)*/
                    estado = Q1;
                    compoeLexema(caractereLido);
                    caractereLido = proximoCaractere();
                }
                else estado = Q2; /**transição (Q1, outro, Q2)*/
                break;
            }
            case Q2: {
                /**verifica se o lexema encontrado é uma palavra reservada
                   e retorna seu token caso contrario insere na tabela de
                   identificadores e retorna o token ID*/
                EntradaTabelaPalavraReservada* entrada;
                entrada = (EntradaTabelaPalavraReservada *) tabelaPalavrasReservadas->buscaEntrada(lexema);
                if(entrada != NULL) return entrada->token;
                else{
                    EntradaTabela *entrada = new EntradaTabela();
                    tabelaIdentificadores->insereEntrada(lexema, entrada);
                    return ID;
                }
                break;
            }
            case Q3: {
                if(isdigit(caractereLido)) {
                    /**transição (Q3, digito, Q3)*/
                    estado = Q3;
                    compoeLexema(caractereLido);
                    caractereLido = proximoCaractere();
                }
                else if(caractereLido == '.') {
                    /**transição (Q3, '.', Q5)*/
                    estado = Q5;
                    compoeLexema(caractereLido);
                    caractereLido = proximoCaractere();
                }
                else if((caractereLido == 'e') || (caractereLido == 'E')) {
                    /**transição (Q5, {e E}, Q8)*/
                    estado = Q6;
                    compoeLexema(caractereLido);
                    caractereLido = proximoCaractere();
                }
                else estado = Q4;/**transição (Q3, outro, Q4)*/
                break;
            }
            case Q4: return NUM;
            case Q5: {
                if(isdigit(caractereLido)) {
                    /**transição (Q5, Digito, Q5)*/
                    estado = Q5;
                    compoeLexema(caractereLido);
                    caractereLido = proximoCaractere();
                }
                else if((caractereLido == 'e') || (caractereLido == 'E')) {
                    /**transição (Q5, {e E}, Q8)*/
                    estado = Q6;
                    compoeLexema(caractereLido);
                    caractereLido = proximoCaractere();
                }
                else estado = Q4;/**transição (Q5, outro, Q4)*/
                break;
            }
            case Q6: {
                if(isdigit(caractereLido)) {
                    /**transição (Q6, Digito, Q8)*/
                    estado = Q8;
                    compoeLexema(caractereLido);
                    caractereLido = proximoCaractere();
                }
                else if((caractereLido == '+') || (caractereLido == '-')) {
                    /**transição (Q6, {+ -}, Q7)*/
                    estado = Q7;
                    compoeLexema(caractereLido);
                    caractereLido = proximoCaractere();
                }
                else estado = Q9;/**transição (Q6, outro, Q9)*/
                break;
            }
            case Q7: {
                if(isdigit(caractereLido)) {
                    /**transição (Q7, Digito, Q8)*/
                    estado = Q8;
                    compoeLexema(caractereLido);
                    caractereLido = proximoCaractere();
                }
                else estado = Q9; /**transição (Q7, outro, Q9)*/
                break;
            }
            case Q8: {
                if(isdigit(caractereLido)) {
                    /**transição (Q8, Digito, Q8)*/
                    estado = Q8;
                    compoeLexema(caractereLido);
                    caractereLido = proximoCaractere();
                }
                else estado = Q4; /**transição (Q8, outro, Q4)*/
                break;
            }
            case Q9:
                /**emite erro numero mal formado e reinicia o automato*/
                errosLexicos(NUMERO_MAL_FORMADO, retornaContadorLinha(), retornaContadorColuna(), caractereLido);
                reiniciaAutomato();
                break;
            case Q10: {
                if(isdigit(caractereLido)) {
                    /**transição (Q10, Digito, Q5)*/
                    estado = Q5;
                    compoeLexema(caractereLido); /**consome o caractere lido*/
                    caractereLido = proximoCaractere();
                }
                /***transição (Q10, outro, Q11)*/
                else estado = Q11; /**não consome o caractere lido*/
                break;
            }
            case Q11: return PONTO;
            case Q12: return VIRGULA;
            case Q13: return PONTOEVIRGULA;
            case Q14: return DOISPONTOS;
            case Q15: return IGUAL;
            case Q16: return MAIS;
            case Q17: return MENOS;
            case Q18: return MULTIPLICACAO;
            case Q19: return DIVISAO;
            case Q20: {
                switch(caractereLido) {
                    case '>':
                        /**transição (Q20, >, Q22)*/
                        estado = Q22;
                        compoeLexema(caractereLido);
                        caractereLido = proximoCaractere();
                        break;
                    case '-':
                        /**transição (Q20, -, Q23)*/
                        estado = Q23;
                        compoeLexema(caractereLido);
                        caractereLido = proximoCaractere();
                        break;
                    case '=':
                        /**transição (Q20, =, Q34)*/
                        estado = Q24;
                        compoeLexema(caractereLido);
                        caractereLido = proximoCaractere();
                        break;
                    default:
                        /**não consome o caractere lido*/
                        /**transição (Q20, outro, Q21)*/
                        estado = Q21;
                        compoeLexema(caractereLido);
                        break;
                }
                break;
            }
            case Q21: return MENOR;
            case Q22: return DIFERENTE;
            case Q23: return ATRIBUICAO;
            case Q24: return MENOROUIGUAL;
            case Q25:
                 /**estado maior >*/
                if(caractereLido == '=') {
                     /**transição (Q25, =, Q27)*/
                    estado = Q27;
                    compoeLexema(caractereLido); /**Consome o caractere = */
                    caractereLido = proximoCaractere();
                }
                else estado = Q26;  /**transição (Q25, Outro, Q26) Não consome o caractere*/
                break;
            case Q26: return MAIOR;
            case Q27: return MAIOROUIGUAL;
            case Q28: return ABREPARENTESES;
            case Q29: return FECHAPARENTESES;
            case Q30: return ABRECOLCHETES;
            case Q31: return FECHACOLCHETES;
            case Q32: {
                /**estado de literais*/
                bool achouFimdeArquivo = false;
                while(caractereLido != '"') {
                    estado = Q32;
                    compoeLexema(caractereLido);
                    caractereLido = proximoCaractere();
                    if(caractereLido == EOF){
                        /**transição (Q2, EOF, Q34)*/
                        estado = Q34;
                        achouFimdeArquivo = true;
                        break; /** para sair do while*/
                    }
                }
                if (!achouFimdeArquivo){
                    /**saiu do while sem achar fim de arquivo
                       Transição (Q32, ", Q33)*/
                    estado = Q33;
                    //caractereLido = proximoCaractere(); /**consome as aspas*/
                }
                break;/**para sair do case*/
            }
            case Q33:
                /**estados de literais
                   insere o literal na tabela de simbolos
                   consome o ultimo caractere lido e retona LITERAL*/
                EntradaTabela* entrada;
                entrada = new EntradaTabela();
                tabelaLiterais->insereEntrada(retornaLexema(),entrada);
                caractereLido = proximoCaractere();
                return LITERAL;
            case Q34:
                /**Erros literal não fechado)*/
                errosLexicos(LITERAL_NAO_FECHADO, linhaErro, colunaErro, EOF);
                return EOF;
            case Q35: {
                /**estado de comentarios, não compoes lexemas
                   apenas bombeia os comentarios.*/
                achouFimdeArquivo = false;
                while(caractereLido != '}') {
                     /**transição (Q2, EOF, Q34)*/
                    estado = Q35;
                    caractereLido = proximoCaractere();
                    if(caractereLido == EOF){
                        /**transição (Q5, EOF, Q36)*/
                        estado = Q36;
                        achouFimdeArquivo = true;
                        break;
                    }
                }
                if (!achouFimdeArquivo){
                     /**saiu do while sem sem encontrar EOF, transição (Q35, '}', Q0)*/
                    estado = Q0;
                    caractereLido = proximoCaractere(); /**consome o caractere '}'*/
                }
                break;
            }
            case Q36:
                /**Erros comentario não fechado)*/
                errosLexicos(COMENTARIO_NAO_FECHADO, linhaErro, colunaErro, EOF);
                return EOF;
            case Q37: return EOF;
            case Q38:
                 /**Erros caracatere invalido)*/
                errosLexicos(CARACTERE_INVALIDO, retornaContadorLinha(), retornaContadorColuna(), caractereLido);
                caractereLido = proximoCaractere(); /**consome o caractere lido*/
                reiniciaAutomato();
                break;

        }
    }
}

void imprimeTabelas(){

    int token;
    token = proximoToken();
    while (token != EOF){
        if (token == ID || token == NUM)
            fprintf(stdout,"%s.%s\n", vetorTokens[token-INICIO], retornaLexema());
        else
            fprintf(stdout,"%s\n", vetorTokens[token-INICIO]);
        token = proximoToken();
    };
    fprintf(stdout,"FIM DE ARQUIVO\n");

    fprintf(stdout,"\n");
    fprintf(stdout, "TABELA DE SIMBOLOS: PALAVRAS RESERVADAS\n");
    fprintf(stdout, "---------------------------------------\n");
    fprintf(stdout, "LEXEMA                   TOKEN NUMERICO\n");
    fprintf(stdout, "---------------------------------------\n");

    EntradaTabelaPalavraReservada *entrada;

    for(int i = 0; i < TAMANHO; i++) {
        entrada = (EntradaTabelaPalavraReservada *) tabelaPalavrasReservadas->tabelaSimbolos[i];
        while(entrada != NULL) {
            fprintf(stdout, "%-25s%d\n", vetorTokens[entrada->token - INICIO], entrada->token);
            entrada = (EntradaTabelaPalavraReservada *)entrada->proximaEntrada;
        }
    }

    fprintf(stdout, "\n");
    fprintf(stdout, "TABELA DE SIMBOLOS: IDENTIFICADORES\n");
    fprintf(stdout, "-----------------------------------\n");

    EntradaTabela *entradaId;

    for(int i = 0; i < TAMANHO; i++) {
        entradaId = tabelaIdentificadores->tabelaSimbolos[i];
        while(entradaId != NULL) {
            fprintf(stdout, "%-25s \n", tabelaIdentificadores->retornaLexema(entradaId));
            entradaId = entradaId->proximaEntrada;
        }
    }
    fprintf(stdout, "\n");


    fprintf(stdout, "\n");
    fprintf(stdout, "TABELA DE SIMBOLOS: LITERAIS\n");
    fprintf(stdout, "----------------------------\n");

    EntradaTabela *entradaLit;

    for(int i = 0; i < TAMANHO; i++) {
        entradaLit = tabelaLiterais->tabelaSimbolos[i];
        while(entradaLit != NULL) {
            fprintf(stdout, "%-25s \n", tabelaLiterais->retornaLexema(entradaLit));
            entradaLit = entradaLit->proximaEntrada;
        }
    }
    fprintf(stdout, "\n");
}

char* retornaNomeToken(int token){
    return vetorTokens[token-INICIO];
};

void destroiAnalex(){
    delete(tabelaIdentificadores);
    delete(tabelaLiterais);
    delete(tabelaPalavrasReservadas);
    fclose(arquivo);
    free(lexema);
}
